
This Week With My Coleco ADAM by Richard F. Drushel 


I.  Rescuing the Coleco ADAM Forum on the Cleveland Freenet.

     A couple weeks ago, a general notice was posted by the
administrators of the Cleveland Freenet, to the effect that they
were going to clean up (i.e., remove) any areas and SIGs which 
they deemed to be dead or abandoned. Unfortunately, the Coleco 
ADAM Forum was one of the SIGs that was slated for removal.  I 
must admit, other than reposting my TWWMCA articles to the
general bulletin board, I hadn't done much with it; and various
technical glitches have prevented us from getting B.A.S.I.C. 
members Pat Williams and Jean Davies on-line and participating.  
Fortunately, as I am one of the sysops, I was able to intervene
and save the Coleco ADAM Forum from destruction.  I updated some 
of the informational text files, redid the menu structure a 
little, and have at least planned how I want to revamp the
newsgroup and bulletin board areas which we have.  If any of you 
have accounts on the Cleveland Freenet, all you have to do is 
type "go adam" at any "Your Choice ==>" prompt.  If you don't 
have an account on CFN, but have telnet access and want to check
us out, then do the following:   telnet://freenet-in-a.cwru.edu
You'll get a nice ASCII art picture of the Cleveland skyline, 
and then the following prompt:  
Are you: 
1. A registered user
2. A visitor
Please enter 1 or 2:

You can enter "2".  You will then get another prompt:

Would you like to:
1. Apply for an account
2. Explore the system
3. Exit the system
Please enter 1, 2 or 3:

You can enter "2" again, and you'll be able to look around the
system. 

Once you get to a "Your Choice ==>" prompt, you should type 
"term" and set your terminal type (probably "vt100" would be 
best).  Then you can "go adam" and look at the information files 
and newsgroups.  Unfortunately, you won't be able to post any-
thing if you are just a visitor.  You can, however, apply for
an account, as indicated on the menu above; accounts on CFN are
free.  I welcome any suggestions about improving the look of the
Coleco ADAM Forum.  Let me know what you think!  You can reach me
and the other sysops (Herman Mason and George Koczwara) by sending
mail to xx001@po.cwru.edu. 

II.  Interrupts and Shared System Resources.

In last week's TWWMCA (9709.07), I promised to talk about
the problem of the non-reentrancy of EOS VRAM routines, and a 
solution that I implemented as part of the EOS-8 project.

     What do I mean by "non-reentrancy"?  Basically it means that 
VDP (video display processor) is hardware which can only be acc-
essed by one user (or subroutine or process) at a time, and while 
that user is accessing it, nobody else dare interrupt him, or the
VDP will get messed up or confused.  Think how hard it is to 
concentrate if you're talking to someone on the telephone
and then your kids pick up the extension phones and all start
talking at once.  Unless you have remarkable powers of concent-
ration, the phone is a non-reentrant resource--only one person
can talk intelligibly at a time.  The Z80 microprocessor allows
external conditions (such as a keyboard or button being pressed,
a character arriving at a modem) to generated a signal called an 
interrupt.  When an interrupt is received, the Z80 finishes the
last machine code instruction it was executing, and then jumps to
some special locations in memory and starts executing whatever 
program is there.  (The programmer has to plan for this and
provide the appropriate program at the appropriate location.)  
This program is called an interrupt service routine (ISR), 
because it is supposed to deal in some special way with whatever
condition caused the interrupt.  For example, in the case of a 
keyboard interrupt, the ISR might read the key that was pressed 
and save an ASCII code for it in a buffer somewhere.  The ISR 
ends with a special form of the RET (return) instruction, which 
tells the Z80 to go back to the program that it had been working 
with and keep going, right where it left off.  Interrupts are an 
efficient alternative to polling, which means sitting in a loop
asking "Are you ready yet?  Are you ready yet?" over and over 
until the answer is "Yes".  With interrupts, when it's ready, it
will come grab *you*, so you can do other things until you get 
grabbed.     How might the VDP get interrupted?  It turns out 
that the VDP itself generates an interrupt 60 times per second 
(50 for European ADAMs running on 50 Hz line current instead of 
60 Hz).  What if the main program was in the middle of a write 
to the VDP (say to display a character on the video screen)
when this VDP interrupt occurred, and the VDP ISR also tried to 
write to the screen?  The result would be garbage on the screen 
and a confused VDP, because it hadn't finished the first write 
before starting the second.  The issue is not academic for
the VDP, because of a quirk in the way the VDP responds to 
interrupts.  Any VDP ISR must have, as its last action before 
returning from the interrupt, a read of VDP register 7 in order
to acknowledge the interrupt and enable it for another cycle 
1/60th (1/50th) of a second later.  If *any* VDP read or write 
is already in progress, this acknowledgment read will disrupt it
and leave the VDP in an unstable state.  There are two possible 
solutions to the problem:  (1) keep the VDP interrupt disabled, 
so the main program can never get interrupted; or (2) somehow 
keep track of when the VDP is being used by a user program, so 
that an interrupt routine won't try to access the VDP if it's 
busy, but will instead wait until another time when the VDP is 
free).  
SmartBASIC 1.0 uses both methods in different places.  (1) In
TEXT mode, the main program can PRINT to the screen, while the 
VDP interrupt routine manages blinking on and off if the FLASH 
command is active.  Blinking can't safely occur, however, if a
PRINT is in progress.  SmartBASIC uses a status flag to indicate
that PRINT is active, and the FLASH interrupt routine leaves the 
VDP alone; however, the FLASH interrupt routine also maintains a
status flag for PRINT to tell it that an interrupt has occurred 
and that PRINT needs to do the acknowledgment read of VDP regist-
er 8, to restart the VDP interrupt for the next cycle.  The 
result of this handshaking is that access to the VDP is regulated
to one routine at a time.  The FLASH rate during a PRINT is 
visibly slower, however, than if you are just sitting at the ] 
prompt.  (Try this:  TEXT, FLASH, then CATALOG some disk/tape
that will cause the directory listing to scroll off the screen.  
Observe the difference in blinking rate.)  (2) In the graphics 
modes (GR, HGR, and HGR2), the VDP interrupt is turned off com-
pletely.  Thus, the main program can read/write the VDP with 
impunity.  Returning to TEXT mode restarts the VDP interrupt.
(In SmartBASIC 1.x, a software clock is installed as part of the
VDP interrupt routine, ticking at 60 (50) times per second; the 
clock stops whenever you are in a graphics mode, because the VDP
interrupt is disabled.) 
III.  Interrupt Deferral in EOS-8 VRAM Routines:  Genesis.

     At ADAMcon IV, whence the EOS-8 project was hatched, one 
of the topics of discussion was this very problem of non-re-
entrancy of the EOS VDP I/O routines.  Based upon study of 
disassembled EOS code, Bruce Walters had suggested a particular 
code fix which he thought might solve the problem. In brief, the 
current EOS-5 code reads/writes the VDP one byte at a time,
using IN/OUT instructions in a software loop.  The Z80 also
provides instructions which can read/write multiple bytes to/
from a memory buffer without a software loop, namely  INIR/OUTIR
(in/out-increment-repeat).  These commands use the HL register 
as a pointer to the buffer, the C register as the port to read/
write, and the B register as a counter.  Data is transferred
via port C; HL is incremented and B is decremented; transfer 
stops when B is zero.  Bruce had hoped that INIR/OUTIR, being 
single commands, were not interruptable (i.e., they would not
respond to an interrupt until B=0); but unfortunately, they *are*
interruptable. 
     I reproduce below a post to the Programmer's Forum on Mark 
Gordon's Micro Innovations BBS, dated 31 July 1992 (about 2 weeks
after ADAMcon IV), in which Chris Braymen and I discuss the issue.
It was in writing this post that I thought of a mechanism for VDP
interrupt deferral for the EOS VRAM routines.


** VDP INTERRUPT HANDLING DISCUSSION**

[Note:  I am using Internet E-mail format to respond to the tech-
nical discussion, so I can quote relevant parts of previous posts
in my response.  The T-BBS message editor does not allow this. 
-- Rich Drushel (75) ]

In a previous article, Chris Braymen (6) says:
>Hi Folks:  A discussion took place at ACIV that I want to make 
sure I understand..  The PROBLEM: You will end up with scramb-
led graphics if you  read VDP register 8 (to restart the video 
interrupt) while you are in the process of accessing the VDP.
According to "The TMS 9118/28/29 Data Manual" (Texas Instruments,
1984),  "In an interrupt-driven environment (CPUs accepting
interrupts), it is possible for an interrupt to occur before any 
one of the [multi-byte VDP access] sequences is finished.  For 
example, an interrupt may occur immediately after loading address
byte 1 or 2 during a write to VRAM operation.  In this case, the
interrupt service routine does not know where the interrupt occ-
urred within the sequence.  Therefore, it is necessary to disable
and enable interrupts before and after every setup sequence.  
This action sequence prevents loss of continuity between the
CPU and the VDP" (p. 2-9).  This means that *ANY* VRAM access
(read data, write data, write register, read register 8) will be
corrupted if an interrupting routine also tries to access VRAM.

>Most Coleco software solves this problem by maintaining a couple
of flags that effectively delay the video interrupt restart until
after the application is done using the VDP.  ColecoVision games 
employ an OS-7 routine which defers the writing of graphical ob-
jects (as defined with certain data structures) until desired
by the programmer.  Usually, VDP writes are deferred *until* an 
NMI occurs. At that point, the VDP is uninterruptable, so all the
deferred writes are done until the deferral queue (or stack) is 
empty.  This routine works only with the special graphical ob-
jects, not low-level VDP reads/writes or register access.  In 
SmartBASIC, NMIs are simply disabled in GR, HGR and HGR2, ducking
the problem.  In TEXT mode, NMIs are enabled, and the NMI routine
changes the pointer between normal and inverse pattern name tables
(if FLASH is enabled) after a defined number of cycles.  This is
fine except that PRINT accesses the VRAM, as well as the actual 
TEXT command. 
The solutions are:  The NMI routine checks to see if PRINT is 
executing, and if so, just does RETN (with no register 8 read to 
 restart the NMI).  The PRINT routine itself maintains the FLASH 
counter and will switch the name table pointers, and then issue a
register 8 read.  (Check the FLASH frequency when sitting waiting 
at the prompt versus when text is PRINTing and scrolling on the 
screen; it is noticeably slower while PRINTing.)  Other programs 
leave the NMI disabled throughout. 
>A proposal was made that this problem could be solved in the EOS
system software by using OTIR instructions in place of the cur-
rent OUT and OUTI instructions.  Having reexamined my disassembly
of EOS-5, I believe that this is no longer a viable solution.  
See below.  
>QUESTIONS:  My understanding of this problem holds that:
>
>      A) The problem occurs as a direct result of reading VDP
register 8 and has absolutely nothing to do with the amount of 
time spent away from the VDP operation at hand during interrupt
processing.
*Any* VRAM register read/write, or VDP data read/write, is vul-
nerable if interrupted by a routine which also attempts to access
VRAM.  It is not restricted to the register 8 read to restart the
NMI for another cycle. Remember that the register 8 read is
*OBLIGATORY* to restart the NMI; RETN is not sufficient.

>      B)  The problem will occur if register 8 is read between 
sending the 2 bytes that make up a VDP command, OR if register 8 
is read between reading or writing consecutive data using the 
auto incrementing VRAM address.   This is correct.

>Some people at the table indicated the problem only exists dur-
ing VDP command access because the EOS already uses OTIR and 
INIR for data reads and writes.  This is not true!  The EOS 
functions Read_VRAM and  Write_VRAM use OUTI and INI enclosed in
loops, they do NOT use the non-interruptable commands OTIR and
INIR.  You are correct.  What's more, the register writes use 
separate OUT commands, the space between which is vulnerable to 
interrupts.  [note added 9709.14:  OTIR and INIR *are* interrupt-
able, so the point is moot]

>So my questions are:
>     1)  Am I correct in assuming the VDP hardware will get 
messed up if >register 8 is read during Command sending OR during
data read and write?  You are correct.

 >     2) If so, will the EOS rewrite use OTIR and INIR in place 
of OUTI and INI in the Read_VRAM and write_VRAM routines? It 
can't, because of 3) below. 

>     3) If so, how will you incorporate the 2 NOP delay for the
"Slow" >VDP between each read/write?  You can't.  So unless 
Bruce's code with OTIR and INIR actually does work, meaning the 
"slow" VDP on the design board wasn't really so "slow" in pract-
ice, I don't think you can use OTIR and INIR to make the write/
read uninterruptable.
>And most importantly <grin>, if the OTIR and INIR opcodes are
un-interruptable (except by DMA), how can I make sure I'm not 
losing MIDI bytes during large transfers to the VDP?  Only by 
not using OTIR and INIR :)  
>RECOMMENDATIONS:  I recommend leaving the system VDP access
completely interruptable and making it the application program-
mers responsibility to handle the Video interrupt restart at a 
time when he/she is not doing anything else with the VDP.
I agree 100% here.  However, I would like to propose a mechanism
whereby interrupt routines *WHICH ACCESS VRAM* could do so with-
out fear of corrupting a main program VRAM access in progress.
[description omitted; see IV below]

>I can live with 2 byte interrupt disabling transfers, but I'll 
have trouble >with any interrupt disabling transfer that's 
bigger than about 80 bytes.  If there isn't a bug in what I just
proposed, you should be safe.  INTs are enabled throughout all 
EOS VRAM function calls, and as long as your INT routine does 
not access VRAM, you could ignore the above. 
>CLOSING: Please take this into consideration when designing the
new EOS.   I am trying :)

>Spinner applications may also be adversely affected by an OTIR
in write_VDP.  Absolutely.   >Thanks, Chris Braymen (6)

Regards, Rich Drushel (75)

******************************


IV.  Interrupt Deferral in EOS-8 VRAM Routines:  Algorithms.

      I reproduce here the design summary of the VRAM interrupt
deferral routines for EOS-8, as posted to the Programmer's Forum
on Mark Gordon's Micro innovations BBS on 6 October 1992.  The 
algorithms are presented as pseudocode and as skeleton assembler.
The complete assembler version as used in EOS-8 (and in a proof-
of-concept version of SmartBASIC 1.x) can be found via anonymous
ftp at:  ftp://junior.apk.net/pub/users/drushel/vram_def.asm    
****************************************************************
EOS-8 VRAM INTERRUPT DEFERRAL ROUTINES.
designed by Richard F. Drushel  6 October 1992

Here is a mechanism whereby interrupt routines *WHICH ACCESS
VRAM* could do so without fear of corrupting a main program VRAM
access in progress.  (If your interrupt routine does not access 
VRAM, there is no problem).  It would work best with the NMI 
(because of its relatively low frequency; an INT can occur at 
any frequency) but might be useful for INTs as well.  It would 
not improve existing interrupt routines, but could be used as a
paradigm for future ones or rewrites.  Anyway, code would be 
required both in the interrupt routine and in each EOS routine 
which accesses VRAM.  EOS would define an interrupt status byte
with 3 flag bits: 

bit 0  ==>  vram_access 
	1 if an EOS function call accessing
                                    
VRAM is in progress, 0 if not. 
     	bit 1  ==>  nmi_request 
	1 if an NMI routine needs                                 
	to be executed, 0 if not.
	bit 2  ==>  int_request       
	1 if an INT routine needs
        to be executed, 0 if not.

The handshaking of interrupt deferral is such that it is
impossible to defer *BOTH* NMIs and INTs simultaneously.  
Indeed, since a READ_REGISTER (read VDP register 8) is 
required to restart NMIs after each NMI cycle, this effective-
ly means that INT routines which access VRAM *cannot* be
deferred if the NMI is active.  The logic of the implement-
ation follows: 
INT:      DI                        
   ;disable further INTs
          IF vram_access=1          
   ;if VRAM I/O is in progress...
             THEN int_request=1     
   ;...then request a deferred INT
                  RET               
   ;and return to VRAM I/O
                                    
   ;*NOTE* INTs *DISABLED*
             ELSE int_request=0     
   ;else wipe the INT request flag
                  CALL do_int       
   ;do the user INT routine
                  EI                
   ;reenable INTs
                  RETI              
   ;return from maskable interrupt
          ENDIF
do_int:   {user INT routine goes
here}
          RET

NMI:      IF vram_access=1          
   ;if VRAM I/O is in progress...

             THEN nmi_request=1     
   ;...then request a deferred NMI
                  RET               
   ;and return to VRAM I/O
                                    
   ;*NOTE* NMIs *DISABLED*
             ELSE nmi_request=0     
   ;else wipe the NMI request flag
                  CALL do_nmi       
   ;do the user NMI routine
                  read VDP register 8
    ;restart the NMIs
                  RETN              
   ;return from non-maskable interrupt
          ENDIF
do_nmi:   {user NMI routine goes here}

          RET

EOSVRAM:  IF vram_access=1          
   ;if VRAM I/O is already in progress...
                                    
   ;i.e. this is a VRAM call made from 
                                    
   ;inside another VRAM call, already
                                    
   ;flagged
             THEN JR do_vram_routine
   ;...then just do it
                                    
   ;this allows the interrupt routine to

   ;use EOS VRAM functions, where it is
                                    
   ;safe from interruption
             ELSE vram_access=1     
   ;else mark that we're doing VRAM I/O
                                    
   ;*NOTE* now we can't be interrupted;
                                    
   ;INTs and NMIs will be deferred
                  CALL
do_vram_routine  
    ;do the VRAM routine without fear
                  IF int_request=1  
   ;now if a deferred INT occurred
                                
   ;while we were doing the VRAM I/O...
                     THEN
int_request=0 ;...then clear the request
                          CALL
do_int   ;do the user INT routine
                         
vram_access=0 ;clear the VRAM usage flag
                          EI        
   ;*FINALLY* reenable INTs
                          RETI      
   ;and return from the interrupt
                                    
   ;We have done our main VRAM I/O
*and*
                                   
   ;our INT VRAM I/O.
                     ELSE
                  ENDIF
                  IF nmi_request=1  
   ;now if a deferred NMI occurred
                                    
   ;while we were doing the VRAM I/O...
                     THEN
nmi_request=0 ;...then clear the request
                          CALL
do_nmi   ;do the user NMI routine
                         
vram_access=0 ;clear the VRAM usage flag
                          read VDP

reg8 ;*FINALLY* reenable NMIs
                          RETN      
   ;and return from the interrupt
                                    
   ;We have done our main VRAM I/O
*and*
                                    
   ;our NMI VRAM I/O.
                      ELSE
                   ENDIF
                   vram_access=0    
   ;all done using VRAM
                   RET              
   ;we're done!


The actual implementations are:

INT:
A56:
      PUSH AF                  ;save
register
      LD A,(INTERRUPT_FLAGS)  
;point to interrupt flags
      BIT 0,A                  ;is a
VRAM I/O in progress?
      JR Z,A71                 ;NO,
so just do the routine
      SET 2,A                  ;YES,
so request a deferred INT
      LD (INTERRUPT_FLAGS),A   ;save
it back
      POP AF                  
;restore AF
      RET                     
;return with INTs *DISABLED*

A71:
      RES 2,A                 
;clear request flag for safety
      LD (INTERRUPT_FLAGS),A   ;save
it
      CALL A83                 ;do
the user INT routine (doesn't need
                               ;to
PUSH AF)
      POP AF                  
;restore AF
      EI                      
;reenable INTs
      RETI                    
;return from maskable interrupt
A83:
      JP user_int_routine      ;jump
to the actual user code
                               ;it
must save AF, HL and any other used
                              
;registers, and end in RET

NMI:
A102:
      PUSH AF                  ;save
register
      LD A,(INTERRUPT_FLAGS)   ;get
interrupt flags
      BIT 0,A                  ;is a
VRAM I/O in progress?
      JR Z,A117                ;NO,
so just do the routine
      SET 1,A                  ;YES,
so request a deferred NMI
      LD (INTERRUPT_FLAGS),A   ;save

it back
      POP AF                  
;restore AF
      RET                     
;return with NMIs *DISABLED*
A117:
      RES 1,A                 
;clear request flag for safety
      LD (INTERRUPT_FLAGS),A   ;and
save it back
      CALL A133                ;do
the user NMI routine (doesn't need
                               ;to
PUSH AF)
      IN A,(191)              
;restart the NMI by reading VDP
register 8
      LD (VDP_STATUS_BYTE),A   ;save
it for EOS usage
      POP AF                  
;restore AF
      RETN                    
;return from non-maskable interrupt
A133:
      JP user_nmi_routine      ;jump
to the actual user code
                               ;it
must save AF, HL and any other used
                              
;registers, and end in RET

;end code which must be installed by
the user application

;begin code which is in EOS RAM

VRAM:
      PUSH HL                  ;save
register
      LD HL,INTERRUPT_FLAGS   
;point to interrupt flags
      BIT 0,(HL)               ;is a
VRAM I/O in progress?
      JR NZ,DO_VRAM2           ;YES,
so just do it
                              
;(interrupts are already deferred)
      SET 0,(HL)               ;NO,
so mark it now in progress
      POP HL                  
;restore HL
      CALL DO_VRAM             ;do
the VRAM routine
VRAM_COMMON_EXIT:

      PUSH HL                  ;save
HL
      LD HL,INTERRUPT_FLAGS   
;point to interrupt flags
      BIT 2,(HL)               ;is
there a deferred INT?
      JR NZ,DO_DEF_INT         ;YES,
so do it
      BIT 1,(HL)               ;is
there a deferred NMI?
      JR NZ,DO_DEF_NMI         ;YES,
so do it
      RES 0,(HL)               ;NO,
so no deferred interrupts; all done
      POP HL                  
;restore HL
      RET                      ;bye!
DO_DEF_INT:

      RES 2,(HL)              
;clear request flag
      CALL A83                 ;do
user INT routine
      RES 0,(HL)               ;all
done with VRAM routine
      POP HL                  
;restore HL
      EI                      
;finally reenable INTs
      RETI                    
;return from maskable interrupt
DO_DEF_NMI:
      RES 1,(HL)              
;clear request flag
      CALL A133                ;do
user NMI routine
      RES 0,(HL)              

;clear VRAM flag
      POP HL                  
;restore HL
      PUSH AF                  ;save
AF
      IN A,(191)              
;restart NMIs by reading VDP
register 8
      LD (VDP_STATUS_BYTE),A   ;save
it for EOS
                              
;VDP_STATUS_BYTE is in EOS global
RAM
      POP AF                  
;restore AF
      RETN                    
;return from non-maskable interrupt
DO_VRAM2:

     POP HL
DO_VRAM:
     {routine here}
      RET

*********************************************************

V.  For Next Time.

     If I get any questions or other expressions of interest 
from the readers, I can talk about a few more details of the 
interrupt deferral code, which have been omitted above.  For
instance, there are some EOS VRAM routines which *cannot* be
deferred; the technical reasons for this are perhaps of some
interest. 
     Otherwise, I will start to write a technical description
of ADAMserve (which I have been meaning to do for quite some
time). 

     See you next week!

     *Rich*

